import { asyncForEach } from '../common/asyncForEach'
import dBInstance from '../db/index'
import { runSuite } from './runSuite'
import { sendWebSocket } from '../common/sendWebSocket'
import { LogStatus, LogTypes } from '../common/enums'
import { BROWSER_WIDTH, BROWSER_HEIGHT } from '../common/consts'

const puppeteer = require('puppeteer')

let browser
export const startRunner = async (data) => {
  if (!browser) {
    // browser = await puppeteer.launch({
    //   headless: false,
    //   ignoreDefaultArgs: ['--enable-automation'],
    //   args: [
    //     '--start-fullscreen',
    //     `--window-size=${BROWSER_WIDTH},${BROWSER_HEIGHT}`,
    //     '-–disable-gpu',
    //     '-–disable-dev-shm-usage',
    //     '-–disable-setuid-sandbox',
    //     '-–no-first-run',
    //     '-–no-sandbox',
    //     '-–no-zygote',
    //     '-–single-process'
    //   ],
    // })
    browser = await puppeteer.connect({
      browserWSEndpoint: 'wss://chrome.browserless.io?token=e73c3673-c020-4d7d-b091-8510485580d5',
    });
  }

  const { id: planId, env, baseEnv, captureScreenShot, retryTimesOnError } = data
  const assertBaseEnvStepLogs = {}      // { target_suiteId1_stepId1: {  }, base_suiteId1_stepId1: {  } }
  const assertBaseEnvTotalStepIds = {}      // { suiteId1: ['step1', 'step2'] }
  const assertBaseEnvFinishedStepIds = {}      // { suiteId1: ['step1', 'step2'] }
  const assertBaseEnvFailureSuiteIds = new Set()
  const assertBaseEnvStartSuiteDates = {}

  const onAddLog = async (params) => {
    const [type, data] = params
    const { planId, suiteId, stepId, startEqualToBaseEnvSuite, date, status, screenshot = '' } = data
    status === LogStatus.failure && assertBaseEnvFailureSuiteIds.add(suiteId)

    startEqualToBaseEnvSuite && (assertBaseEnvStartSuiteDates[suiteId] = date)
    sendWebSocket(['add_log', { data: { type, data } }])
    let screenshotId = ''
    if (screenshot) {
      screenshotId = await dBInstance.screenshots.addScreenshot(planId, suiteId, stepId, screenshot)
      data.screenshotId = screenshotId
      delete data.screenshot
    }

    dBInstance.logs.addLog(params)
  }

  const onAddBaseEnvLog = (params) => {
    const [type, data] = params

    sendWebSocket(['base_env_logs_changed', { data: { type, data } }])
    dBInstance.baseEnvLogs.addLog(params)
  }

  const onFinishAssertBaseEnvStep = ({ targetOrBase, suiteId, step, result, relativeStepIds, logParams, error }) => {
    const stepId = step.id
    const genId = isTargetOrBase => `${isTargetOrBase}_${suiteId}_${stepId}`
    const id = genId(targetOrBase)
    const theOtherTargetOrBase = targetOrBase === 'target' ? 'base' : 'target'
    const theOtherId = genId(theOtherTargetOrBase)

    if (assertBaseEnvStepLogs[id]) {
      throw 'already exist'
    } else {
      !assertBaseEnvFinishedStepIds[suiteId] && (assertBaseEnvFinishedStepIds[suiteId] = new Set())
      !assertBaseEnvTotalStepIds[suiteId] && (assertBaseEnvTotalStepIds[suiteId] = new Set(relativeStepIds))
      assertBaseEnvStepLogs[id] = result

      if (typeof assertBaseEnvStepLogs[theOtherId] !== 'undefined') {
        const baseResult = assertBaseEnvStepLogs[genId('base')]
        const targetResult = assertBaseEnvStepLogs[genId('target')]

        const [type, data] = logParams
        const valid = baseResult === targetResult
        !valid && assertBaseEnvFailureSuiteIds.add(suiteId)

        onAddLog([type, ...{
          ...data,
          error,
          status: valid ? LogStatus.pass : LogStatus.failure,
          date: +new Date(),
          result: {
            valid,
            message: '',
            expected: baseResult,   // 基准环境是 expected
            actual: targetResult    // 测试环境是 actual
          }
        }])

        assertBaseEnvFinishedStepIds[suiteId].add(stepId)

        const totalStepIds = Array.from(assertBaseEnvTotalStepIds[suiteId])
        const finishedStepIds = assertBaseEnvFinishedStepIds[suiteId]
        const allFinished = totalStepIds.every(theStepId => finishedStepIds.has(theStepId))

        if (allFinished) {
          const end = +new Date()
          const start = assertBaseEnvStartSuiteDates[suiteId] || end
          onAddLog([LogTypes.suite, { planId, suiteId, status: assertBaseEnvFailureSuiteIds.has(suiteId) ? LogStatus.failure : LogStatus.pass, date: end, elapse: end - start }])
        }
      }
    }
  }

  const threadCount = 6   // 并发多个线程同时运行
  const suiteCollectionOfThread = new Array(threadCount).fill(null).map(() => [])   // 数组中每个元素都是在一个线程运行的用例集合
  data.suites.forEach((suite, index) => {
    const targetIndex = index % threadCount
    suiteCollectionOfThread[targetIndex].push(suite)
  })

  const promiseList = suiteCollectionOfThread.map(async suitesOfSameThread => {
    return new Promise(async (resolve) => {
      await asyncForEach(suitesOfSameThread, async suite => {
        await runSuite({
          browser, onAddLog, onAddBaseEnvLog, onFinishAssertBaseEnvStep,
          env, baseEnv, retryTimesOnError,
          captureScreenShot: captureScreenShot === 'Y', planId, suite
        })
        resolve()
      })
    })
  })

  await Promise.all(promiseList)

  return 'ok'
}